package fr.unice.polytech.chord.core;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

import org.objectweb.proactive.Body;
import org.objectweb.proactive.InitActive;
import org.objectweb.proactive.core.util.ProActiveRandom;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * A tracker assists in the communication between peers. It is used in order to
 * help a peer to join an existing network by storing some remote chord nodes.
 * 
 * @author Anthony Parisy
 * @author Laurent Pellegrino
 * @author Marc Valdener
 * 
 * @version 10/30/2009
 */
@SuppressWarnings("serial")
public class Tracker implements InitActive, Serializable {

	private static final Logger logger = LoggerFactory.getLogger(Tracker.class);

	public static final int CHECK_INTERVAL_TIME = 2000;

	/**
	 * The probability to store a chord node reference when it try to join the
	 * network by using this tracker.
	 */
	private static final int PROBABILITY = 1;

	/**
	 * The chord node references list that the tracker maintains.
	 */
	private List<ChordNode> remotePeers = new ArrayList<ChordNode>();

	/**
	 * Constructor.
	 */
	public Tracker() {
	}

	/**
	 * Add the given {@link ChordNode} on the network that the tracker manages.
	 * 
	 * @param remotePeer
	 *            the peer to add on the network.
	 */
	public void addOnNetwork(ChordNode remotePeer)
			throws IllegalArgumentException {
		if (this.remotePeers.size() == 0) {
			this.remotePeers.add(remotePeer);
			remotePeer.create();
			logger.info("Chord node " + remotePeer.getKey()
					+ " has created the network");
		} else {
			ChordNode peerToJoin = this.getRandomPeer();

			try {
				if (remotePeer.join(peerToJoin)) {
					if (ProActiveRandom.nextInt(Tracker.PROBABILITY) == 0) {
						this.remotePeers.add(remotePeer);
					}
					logger.info("Chord node " + remotePeer.getKey()
							+ " has joined");
				}
			} catch (Exception e) {
				// The remote peer we contact in order to join is died, so we
				// retry with an another
				logger
						.info("A dead peer maintained as entry point has been detected");
				this.remotePeers.remove(peerToJoin);
				this.addOnNetwork(remotePeer);
			}
		}
	}

	/**
	 * Returns the number of peers that the tracker manages.
	 * 
	 * @return the number of peers that the tracker manages.
	 */
	public int getNumberOfPeersManaged() {
		return this.remotePeers.size();
	}

	/**
	 * Returns a random peer from the local list.
	 * 
	 * @return a random peer from the local list.
	 */
	public ChordNode getRandomPeer() {
		if (this.remotePeers.size() == 0) {
			return null;
		}

		return this.remotePeers.get(ProActiveRandom.nextInt(this.remotePeers
				.size()));
	}

	/**
	 * Checks if peers maintains as entry point are alive. If not, their
	 * reference is thrown.
	 */
	private void checkPeers() {
		Tracker.logger.debug("Checking peers maintain as entry point...");

		List<ChordNode> failedPeers = new ArrayList<ChordNode>();

		for (int i = 0; i < this.remotePeers.size(); i++) {
			try {
				this.remotePeers.get(i).isAware();
			} catch (Exception e) {
				failedPeers.add(this.remotePeers.get(i));
			}
		}

		for (ChordNode peer : failedPeers) {
			this.remotePeers.remove(peer);
		}
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void initActivity(Body arg0) {
		new Thread(new Runnable() {

			@Override
			public void run() {
				while (true) {
					Tracker.this.checkPeers();
					try {
						Thread.sleep(Tracker.CHECK_INTERVAL_TIME);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
			}
		}).start();
	}
}
